/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/

#include "audio/outputbuffer.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <stdint.h>
#include <sys/time.h>

#define __devloglevel__ 4

#define debugperf 0
#define debugperf2 0

uint32_t getTime() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    uint32_t ret = static_cast<uint32_t>(tv.tv_usec / 1000 + tv.tv_sec * 1000);
    return ret;
}



OutputBuffer::OutputBuffer(JackAudio* jack)
{
    _buffersize=64*1024;
    _jackSamplerate = jack->getSamplerate();
    _ringbuffer = jack_ringbuffer_create(_buffersize);
    _sndfile = nullptr;
    _datacount=0;
    start();
}

void OutputBuffer::scheduleSynth(ISynth *synth)
{
    if(_scheduledSynth) return;
    _datacount = 0;
    _runtime = 0;
    jack_ringbuffer_reset(_ringbuffer);
    open("/tmp/test.wav",44100);
    _scheduledSynth=synth;
}

void OutputBuffer::run()
{
    int data_size = 1024*2;
    float* data = new float[data_size];
    uint32_t lasttime = getTime();
    while(1)
    {
        if(_scheduledSynth)
        {
            int end = _scheduledSynth->readData(data,data_size);
            if(end==0)
            {
                _scheduledSynth->readData(nullptr,0);
                _scheduledSynth= nullptr;
                 close();
                continue;
            }


            int writespace = jack_ringbuffer_write_space(_ringbuffer);
            int size2 = data_size*sizeof(float);

            int sleepcount=0;
            while(writespace<2*size2)
            {
                if(!_playbackIsStable) emit startPlayback();
                _playbackIsStable = true;
                usleep(1000);
                sleepcount++;
                writespace = jack_ringbuffer_write_space(_ringbuffer);
            }

            uint32_t currenttime = getTime();
            uint32_t timedelta = currenttime-lasttime;
            lasttime=currenttime;
            if(debugperf) DEVLOG_DEBUG("sleepcount "+STR(sleepcount)+" writespace "+STR(writespace)+" timedelta "+STR(timedelta));
            _runtime += timedelta;
            uint32_t timedelta_max = (int)(data_size*1000/44100);
            if(debugperf2) if(timedelta>timedelta_max) DEVLOG_DEBUG("timedelta_max "+STR(timedelta_max)+" timedelta "+STR(timedelta-sleepcount));



            jack_ringbuffer_write(_ringbuffer,(char*)data,size2);
            sf_write_float(_sndfile,data,data_size);
        }
        else
        {
            usleep(20000); //nothing to do
        }
    }
}

//on synth start
void OutputBuffer::open(QString fileName,int samplerate){
    if(_sndfile) sf_close(_sndfile);
    SF_INFO info;
    memset(&info,0,sizeof(info));
    info.format =  SF_FORMAT_WAV | SF_FORMAT_PCM_16;
    info.samplerate = samplerate;
    info.channels = 1;
    _sndfile = sf_open(fileName.toUtf8().data(),SFM_WRITE,&info);
    if(_jackSamplerate!=samplerate)
    {
        DEVLOG_ERROR("resampler TODO\n");
        exit(1);
    }
}

void OutputBuffer::openReadFile(QString fileName)
{
    SF_INFO info;
    memset(&info,0,sizeof(info));
    _sndfile = sf_open(fileName.toUtf8().data(),SFM_READ,&info);
    _offline = true;
}

//on synth end
void OutputBuffer::close()
{
    if(_sndfile) sf_close(_sndfile);
    _sndfile = nullptr;
    jack_ringbuffer_reset(_ringbuffer);
    _datacount = 0;
}
//other thread -- copy engine
void OutputBuffer::readData(float *data, int size)
{
    if(_offline && _sndfile)
    {
        int count = sf_readf_float(_sndfile,data,size);
        if(count==0)
        {
            DEVLOG_DEBUG("offline mode: stop playback");
            emit stopPlayback();
            close();
        }
        return;
    }

    //if(!_playbackIsStable) return; //do nothing
    unsigned int data_size = (unsigned int)size*sizeof(float);
    memset(data,0,data_size);

    unsigned int readspace = jack_ringbuffer_read_space(_ringbuffer);
    float filled = readspace*1.0/_buffersize;
    if(filled<0.5 && debugperf) DEVLOG_DEBUG("reading data "+STR(filled));

    if(!_playbackIsStable) return;

    if(readspace>=data_size)
    {
        jack_ringbuffer_read(_ringbuffer,(char*)data,data_size);
    }
    else
    {
        if(_scheduledSynth)
        {
            if(debugperf) DEVLOG_DEBUG("XRUN - cannot read from buffer");
            _xruncount++;
        }
        else
        {
            if(_playbackIsStable)
            {
                DEVLOG_DEBUG("end reached xruncount="+STR(_xruncount)+" cputime="+STR(_runtime));
                stopPlayback();
                _playbackIsStable=false;
            }
        }
    }

}

void OutputBuffer::reset()
{
    jack_ringbuffer_reset(_ringbuffer);
    _datacount = 0;

}

